﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;

namespace Xceed.Silverlight.Data
{
  public sealed class IndexBasedDataSourceProvider : ParentDataSourceProvider
  {
    #region Constructor

    public IndexBasedDataSourceProvider( DataSourceProvider dataSourceProvider )
      : base( dataSourceProvider )
    {
      if( dataSourceProvider.GetType() == this.GetType() )
        throw new ArgumentException( "Cannot reference a DataSourceProvider of the same type.", "dataSourceProvider" );

      if( typeof( IndexDataSourceProvider ).IsAssignableFrom( dataSourceProvider.GetType() ) )
        throw new ArgumentException( "The DataSourceProvider must not be of type IndexDataSourceProvider.", "dataSourceProvider" );

      if( !dataSourceProvider.CanCount )
        throw new ArgumentException( "The DataSourceProvider requires count capabilities.", "dataSourceProvider" );

      if( !dataSourceProvider.CanSkip )
        throw new ArgumentException( "The DataSourceProvider requires skip capabilities.", "dataSourceProvider" );
    }

    #endregion

    #region CanCount Property

    public sealed override bool CanCount
    {
      get
      {
        return true;
      }
    }

    #endregion

    #region CanGroupCount Property

    public sealed override bool CanGroupCount
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanJumpToItem Property

    public sealed override bool CanJumpToItem
    {
      get
      {
        return true;
      }
    }

    #endregion

    #region CanSkip Property

    public sealed override bool CanSkip
    {
      get
      {
        return true;
      }
    }

    #endregion

    #region CanSort Internal Property

    internal sealed override bool CanSort
    {
      get
      {
        return true;
      }
    }

    #endregion

    #region CanJumpToDeletedItem Internal Property

    internal sealed override bool CanJumpToDeletedItem
    {
      get
      {
        return true;
      }
    }

    #endregion

    #region IsPrimaryKeyVolatile Internal Property

    internal sealed override bool IsPrimaryKeyVolatile
    {
      get
      {
        return true;
      }
    }

    #endregion

    protected override void Initialize()
    {
      m_indexHelper = ( base.CanSort )
                        ? new IndexHelper( this.ChildDataSourceProvider, base.PrimaryKeyInfo )
                        : new IndexHelper( this.ChildDataSourceProvider, Enumerable.Empty<SortDescription>() );

      base.Initialize();
    }

    protected override IEnumerable<SortDescription> GetPrimaryKeyInfo()
    {
      yield return new SortDescription( IndexDataItem.IndexKeyName, ListSortDirection.Ascending );
    }

    internal override IEnumerable<FilterOperator> SupportedFilterOperators( Type dataType )
    {
      if( dataType == typeof( IndexKey ) )
        return Enumerable.Empty<FilterOperator>();

      return base.SupportedFilterOperators( dataType );
    }

    internal override bool IsSortOrderSupported( IEnumerable<SortDescription> sortOrder )
    {
      // We must exclude the index key name from the sorted properties since it is
      // unknown to the underlying DataSourceProvider.
      List<SortDescription> newSortOrder = ( from sd in sortOrder
                                             where sd.PropertyName != IndexDataItem.IndexKeyName
                                             select sd ).ToList();

      if( newSortOrder.Count == 0 )
        return true;

      return base.IsSortOrderSupported( newSortOrder );
    }

    internal sealed override bool CanJumpTo( IEnumerable<SortDescription> properties )
    {
      if( !this.CanJump )
        return false;

      return ( properties != null )
          && ( properties.Any( ( item ) => item.PropertyName == IndexDataItem.IndexKeyName ) );
    }

    internal override bool FindElementMemberType( string memberName, out Type type )
    {
      if( memberName == IndexDataItem.IndexKeyName )
      {
        type = typeof( IndexKey );
        return true;
      }

      return base.FindElementMemberType( memberName, out type );
    }

    internal override IAsyncResult BeginExecuteDataQueryCore(
      DataQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      return m_indexHelper.BeginGetItems( queryParameters, callback, state );
    }

    internal override DataQueryResult EndExecuteDataQueryCore( IAsyncResult asyncResult )
    {
      DataQueryResult queryResult = m_indexHelper.EndGetItems( asyncResult );

      List<IndexDataItem> dataItems = new List<IndexDataItem>();
      foreach( IndexDataObject item in queryResult.Data.Cast<IndexDataObject>() )
      {
        RawDataItem dataItem = ( RawDataItem )item.DataObject;
        IndexKey key = new IndexKey( this, item.Context, item.Index );
        IndexDataItem newDataItem = new IndexDataItem( dataItem, key );

        dataItems.Add( newDataItem );
      }

      return new DataQueryResult(
               dataItems,
               queryResult.InlineCount,
               queryResult.TotalCount );
    }

    internal override IAsyncResult BeginExecuteStatQueryCore(
      StatQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      return m_indexHelper.BeginGetStatValue( queryParameters, callback, state );
    }

    internal override StatQueryResult EndExecuteStatQueryCore( IAsyncResult asyncResult )
    {
      return m_indexHelper.EndGetStatValue( asyncResult );
    }

    internal override bool TryWrapDataObject( object item, out RawDataItem dataItem )
    {
      IndexDataItem localDataItem = item as IndexDataItem;
      object childItem = ( localDataItem != null )
                           ? localDataItem.ChildDataItem
                           : item;

      // Make sure the child item is of the appropriate type.
      if( !base.TryWrapDataObject( childItem, out dataItem ) )
        return false;

      if( ( localDataItem == null ) || ( !object.Equals( childItem, dataItem ) ) )
      {
        IndexKey primaryKey = new IndexKey( this, DataQueryContext.None );
        localDataItem = new IndexDataItem( dataItem, primaryKey );
      }

      dataItem = localDataItem;

      return true;
    }

    internal override bool TryUnwrapDataObject( RawDataItem dataItem, out object item )
    {
      IndexDataItem localDataItem = dataItem as IndexDataItem;
      if( localDataItem != null )
        return base.TryUnwrapDataObject( localDataItem.ChildDataItem, out item );

      item = null;
      return false;
    }

    internal override bool TryUnwrapLayer( RawDataItem dataItem, out object item )
    {
      IndexDataItem localDataItem = dataItem as IndexDataItem;
      if( localDataItem != null )
      {
        // Make sure the child data item is of the expected type.
        if( base.TryUnwrapLayer( localDataItem.ChildDataItem, out item ) )
        {
          item = localDataItem;
          return true;
        }
      }

      item = null;
      return false;
    }

    #region Private Fields

    private IndexHelper m_indexHelper;

    #endregion

    #region Private IndexHelper Nested Type

    private class IndexHelper : IndexDataSourceProviderHelper
    {
      internal IndexHelper(
        DataSourceProvider dataSourceProvider,
        IEnumerable<SortDescription> defaultSortOrder )
        : base( defaultSortOrder )
      {
        if( dataSourceProvider == null )
          throw new ArgumentNullException( "dataSourceProvider" );

        m_dataSourceProvider = dataSourceProvider;
      }

      protected override bool CanSortBy( IEnumerable<SortDescription> sortOrder )
      {
        return m_dataSourceProvider.IsSortOrderSupported( sortOrder );
      }

      protected override IAsyncResult BeginCountQuery(
        CountQueryParameters queryParameters,
        AsyncCallback callback,
        object state )
      {
        return m_dataSourceProvider.BeginExecuteCountQueryInternal( queryParameters, callback, state );
      }

      protected override CountQueryResult EndCountQuery( IAsyncResult asyncResult )
      {
        return m_dataSourceProvider.EndExecuteCountQueryInternal( asyncResult );
      }

      protected override IAsyncResult BeginDataQuery(
        DataQueryParameters queryParameters,
        AsyncCallback callback,
        object state )
      {
        return m_dataSourceProvider.BeginExecuteDataQueryInternal( queryParameters, callback, state );
      }

      protected override DataQueryResult EndDataQuery( IAsyncResult asyncResult )
      {
        return m_dataSourceProvider.EndExecuteDataQueryInternal( asyncResult );
      }

      protected override IAsyncResult BeginGroupCountQuery( 
        GroupCountQueryParameters queryParameters, 
        AsyncCallback callback, 
        object state )
      {
        throw new NotSupportedException();
      }

      protected override GroupCountQueryResult EndGroupCountQuery( IAsyncResult asyncResult )
      {
        throw new NotSupportedException();
      }

      protected override IAsyncResult BeginStatQuery(
        StatQueryParameters queryParameters,
        AsyncCallback callback,
        object state )
      {
        return m_dataSourceProvider.BeginExecuteStatQueryInternal( queryParameters, callback, state );
      }

      protected override StatQueryResult EndStatQuery( IAsyncResult asyncResult )
      {
        return m_dataSourceProvider.EndExecuteStatQueryInternal( asyncResult );
      }

      private DataSourceProvider m_dataSourceProvider;
    }

    #endregion
  }
}
